// Copyright 2012 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

#include <emscripten/bind.h>
#include <emscripten/em_asm.h>
#include <emscripten/heap.h>
#include <functional>
#include <malloc.h>
#include <string>

#if __cplusplus >= 201703L
#include <optional>
#endif

using namespace emscripten;

val emval_test_mallinfo() {
  const auto& i = mallinfo();
  val rv(val::object());
  rv.set("arena", val(i.arena));
  rv.set("ordblks", val(i.ordblks));
  rv.set("smblks", val(i.smblks));
  rv.set("hblks", val(i.hblks));
  rv.set("usmblks", val(i.usmblks));
  rv.set("fsmblks", val(i.fsmblks));
  rv.set("uordblks", val(i.uordblks));
  rv.set("fordblks", val(i.fordblks));
  rv.set("keepcost", val(i.keepcost));
  return rv;
}

val emval_test_new_integer() {
  return val(15);
}

val emval_test_new_string() {
  return val("Hello everyone");
}

std::string emval_test_get_string_from_val(val v) {
  return v["key"].as<std::string>();
}

val emval_test_new_object() {
  val rv(val::object());
  rv.set("foo", val("bar"));
  rv.set("baz", val(1));
  return rv;
}

struct DummyForPointer {
  int value;
  DummyForPointer(const int v) : value(v) {}
};

static DummyForPointer emval_pointer_dummy(42);

val emval_test_instance_pointer() {
  DummyForPointer* p = &emval_pointer_dummy;
  return val(p);
}

int emval_test_value_from_instance_pointer(val v) {
  DummyForPointer* p = v.as<DummyForPointer*>(allow_raw_pointers());
  return p->value;
}

unsigned emval_test_passthrough_unsigned(unsigned v) {
  return v;
}

val emval_test_passthrough(val v) {
  return v;
}

void emval_test_return_void() {
}

bool emval_test_not(bool b) {
  return !b;
}

bool emval_test_is_true(val v) {
  return v.isTrue();
}

bool emval_test_is_false(val v) {
  return v.isFalse();
}

bool emval_test_is_null(val v) {
  return v.isNull();
}

bool emval_test_is_undefined(val v) {
  return v.isUndefined();
}

bool emval_test_equals(val v1, val v2) {
  return v1.equals(v2);
}

bool emval_test_strictly_equals(val v1, val v2) {
  return v1.strictlyEquals(v2);
}

unsigned emval_test_as_unsigned(val v) {
  return v.as<unsigned>();
}

unsigned emval_test_get_length(val v) {
  return v["length"].as<unsigned>();
}

double emval_test_add(char c,
                      signed char sc,
                      unsigned char uc,
                      signed short ss,
                      unsigned short us,
                      signed int si,
                      unsigned int ui,
                      signed long sl,
                      unsigned long ul,
                      float f,
                      double d) {
  return c + sc + uc + ss + us + si + ui + sl + ul + f + d;
}

float const_ref_adder(const int& i, const float& f) {
  return i + f;
}

unsigned emval_test_sum(val v) {
  unsigned length = v["length"].as<unsigned>();
  double rv = 0;
  for (unsigned i = 0; i < length; ++i) {
    rv += v[i].as<double>();
  }
  return rv;
}

struct DestructorCounter {
  static int count;
  ~DestructorCounter() {
    count++;
  };
};

int DestructorCounter::count = 0;

void emval_test_callback_arg_lifetime(val callback) {
  DestructorCounter dc;
  int destructorCount = DestructorCounter::count;
  callback(dc);
  assert(destructorCount == DestructorCounter::count);
}

std::string get_non_ascii_string(bool embindStdStringUTF8Support) {
  if (embindStdStringUTF8Support) {
    // ASCII
    std::string testString{"aei"};
    // Latin-1 Supplement
    testString += "\u00E1\u00E9\u00ED";
    // Greek
    testString += "\u03B1\u03B5\u03B9";
    // Cyrillic
    testString += "\u0416\u041B\u0424";
    // CJK
    testString += "\u5F9E\u7345\u5B50";
    // Euro sign
    testString += "\u20AC";
    return testString;
  } else {
    char c[128 + 1];
    c[128] = 0;
    for (int i = 0; i < 128; ++i) {
      c[i] = 128 + i;
    }
    return c;
  }
}

std::wstring get_non_ascii_wstring() {
  std::wstring ws(4, 0);
  ws[0] = 10;
  ws[1] = 1234;
  ws[2] = 2345;
  ws[3] = 65535;
  return ws;
}

std::u16string get_non_ascii_u16string() {
  std::u16string u16s(4, 0);
  u16s[0] = 10;
  u16s[1] = 1234;
  u16s[2] = 2345;
  u16s[3] = 65535;
  return u16s;
}

std::u32string get_non_ascii_u32string() {
  std::u32string u32s(5, 0);
  u32s[0] = 10;
  u32s[1] = 1234;
  u32s[2] = 2345;
  u32s[3] = 128513;
  u32s[4] = 128640;
  return u32s;
}

std::wstring get_literal_wstring() {
  return L"get_literal_wstring";
}

std::u16string get_literal_u16string() {
  return u"get_literal_u16string";
}

std::u32string get_literal_u32string() {
  return U"get_literal_u32string";
}

void force_memory_growth() {
  std::size_t old_size = emscripten_get_heap_size();
  EM_ASM({"globalThis.oldheap = HEAPU8;"});
  assert(val::global("oldheap")["byteLength"].as<size_t>() == old_size);
  emscripten_resize_heap(old_size + EMSCRIPTEN_PAGE_SIZE);
  assert(emscripten_get_heap_size() > old_size);
  // HEAPU8 on the module should now be rebound, and our oldheap should be
  // detached
  assert(val::module_property("HEAPU8")["byteLength"].as<size_t>() > old_size);
  assert(val::global("oldheap")["byteLength"].as<size_t>() == 0);
}

std::string emval_test_take_and_return_const_char_star(const char* str) {
  return str;
}

std::string emval_test_take_and_return_std_string(std::string str) {
  return str;
}

std::string emval_test_take_and_return_std_string_const_ref(const std::string& str) {
  return str;
}

std::wstring take_and_return_std_wstring(std::wstring str) {
  return str;
}

std::u16string take_and_return_std_u16string(std::u16string str) {
  return str;
}

std::u32string take_and_return_std_u32string(std::u32string str) {
  return str;
}

std::function<std::string(std::string)> emval_test_get_function_ptr() {
  return emval_test_take_and_return_std_string;
}

std::string emval_test_take_and_call_functor(std::function<std::string(std::string)> func) {
  return func("asdf");
}

class ValHolder {
public:
  ValHolder(val v) : v_(v) {}

  val getVal() const {
    return v_;
  }

  val getValNonConst() {
    return v_;
  }

  const val getConstVal() const {
    return v_;
  }

  const val& getValConstRef() const {
    return v_;
  }

  void setVal(val v) {
    this->v_ = v;
  }

  static int some_class_method(int i) {
    return i;
  }

  static const ValHolder* makeConst(val v) {
    return new ValHolder(v);
  }

  static ValHolder makeValHolder(val v) {
    return ValHolder(v);
  }

  static void set_via_raw_pointer(ValHolder* vh, val v) {
    vh->setVal(v);
  }

  static val get_via_raw_pointer(const ValHolder* vh) {
    return vh->getVal();
  }

  static void transfer_via_raw_pointer(ValHolder* target,
                                       const ValHolder* source) {
    target->setVal(source->getVal());
  }

  static val getValNonMember(const ValHolder& target) {
    return target.getVal();
  }

private:
  val v_;
};

ValHolder emval_test_return_ValHolder() {
  return val::object();
}

val valholder_get_value_mixin(const ValHolder& target) {
  return target.getVal();
}

void valholder_set_value_mixin(ValHolder& target, const val& value) {
  target.setVal(value);
}

void emval_test_set_ValHolder_to_empty_object(ValHolder& vh) {
  vh.setVal(val::object());
}

class StringHolder {
public:
  StringHolder(const std::string& s) : str_(s) {}

  void set(const std::string& s) {
    str_ = s;
  }

  std::string get() const {
    return str_;
  }

  std::string& get_ref() {
    return str_;
  }

  const std::string& get_const_ref() const {
    return str_;
  }

private:
  std::string str_;
};

class SharedPtrHolder {
public:
  SharedPtrHolder() : ptr_(new StringHolder("a string")) {}

  std::shared_ptr<StringHolder> get() const {
    return ptr_;
  }

  void set(std::shared_ptr<StringHolder> p) {
    ptr_ = p;
  }

private:
  std::shared_ptr<StringHolder> ptr_;
};

class VectorHolder {
public:
  VectorHolder() {
    v_.push_back(StringHolder("string #1"));
    v_.push_back(StringHolder("string #2"));
  }

  std::vector<StringHolder> get() const {
    return v_;
  }

  void set(std::vector<StringHolder> vec) {
    v_ = vec;
  }

private:
  std::vector<StringHolder> v_;
};

class SmallClass {
public:
  SmallClass() : member(7){};
  int member;
};

class BigClass {
public:
  BigClass() : member(11){};
  int member;
  int otherMember;
  int yetAnotherMember;

  int getMember() {
    return member;
  }
};

class NoExceptClass {
public:
  int getValue() noexcept {
    return 42;
  }
  int getValueConst() const noexcept {
    return 43;
  }
  int getX() const noexcept { return x; }
  void setX(int x_) noexcept { x = x_; }

private:
  int x;
};

void embind_test_no_except_function(NoExceptClass&) noexcept {}

class ParentClass {
public:
  ParentClass() : bigClass(){};

  BigClass bigClass;

  const BigClass& getBigClass() {
    return bigClass;
  };
};

template<typename T> class TemplateClass {
public:
  TemplateClass(T a, T b, T c) {
    members[0] = a;
    members[1] = b;
    members[2] = c;
  };

  const T getMember(int n) {
    return members[n];
  }

protected:
  T members[3];
};

class ContainsTemplatedMemberClass {
public:
  ContainsTemplatedMemberClass() : testTemplate(86, 87, 88){};

  TemplateClass<int> testTemplate;

  const TemplateClass<int>& getTestTemplate() {
    return testTemplate;
  };
};

class SymbolNameClass {
public:
  std::string iterator() {
    return "Iterator";
  }

  static std::string species() {
    return "Species";
  }
};

// Begin Inheritance Hierarchy Class Definitions

class Base {
public:
  Base() : name("Base"), member(0), baseMember(0) {}

  std::string getClassName() const {
    return name;
  }
  std::string getClassNameFromBase() const {
    return name;
  }
  std::string getClassNameNotAvailableInDerivedClasses() {
    // but wait -- if you act now we will throw in a SECOND base class method
    // ABSOLUTELY FREE!!
    return name;
  }
  void setMember(int value) {
    member = value;
  }
  int getMember() {
    return member;
  }
  void setBaseMember(int value) {
    baseMember = value;
  }
  int getBaseMember() {
    return baseMember;
  }
  static std::string classFunction() {
    return "Base";
  };
  std::string name;
  int member;
  int baseMember;
};

class SecondBase {
public:
  SecondBase() : name("SecondBase"), member(0), secondBaseMember(0) {}

  std::string getClassName() const {
    return name;
  }
  std::string getClassNameNotAvailableInDerivedClasses() {
    return name;
  }
  std::string getClassNameFromSecondBase() const {
    return name;
  }
  void setMember(int value) {
    member = value;
  }
  int getMember() {
    return member;
  }
  void setSecondBaseMember(int value) {
    secondBaseMember = value;
  }
  int getSecondBaseMember() {
    return secondBaseMember;
  }
  std::string name;
  int member;
  int secondBaseMember;
};

class Derived : public Base {
public:
  Derived() : Base(), member(0), name_("Derived") {
  }

  std::string getClassName() const {
    return name_;
  }
  void setMember(int value) {
    member = value;
  }
  int getMember() {
    return member;
  }
  static std::string classFunction() {
    return "Derived";
  }
  int member;

private:
  std::string name_;
};

class DerivedHolder {
public:
  DerivedHolder() {
    derived_.reset();
  }
  void newDerived() {
    deleteDerived();
    derived_ = std::shared_ptr<Derived>(new Derived());
  }
  void deleteDerived() {
    derived_.reset();
  }
  std::shared_ptr<Derived> getDerived() {
    return derived_;
  }
  std::string getDerivedClassName() {
    return derived_->getClassName();
  }

private:
  std::shared_ptr<Derived> derived_;
};

class SiblingDerived : public Base {
public:
  SiblingDerived() : Base(), name_("SiblingDerived") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class MultiplyDerived : public Base, public SecondBase {
public:
  MultiplyDerived() : Base(), SecondBase(), name_("MultiplyDerived") {
    instanceCount_++;
  }

  ~MultiplyDerived() {
    instanceCount_--;
  }

  std::string getClassName() const {
    return name_;
  }

  static int getInstanceCount() {
    return instanceCount_;
  }

private:
  std::string name_;
  static int instanceCount_;
};
int MultiplyDerived::instanceCount_ = 0;

class DerivedTwice : public Derived {
public:
  DerivedTwice() : Derived(), name_("DerivedTwice") {
  }

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class DerivedTwiceNotBound : public Derived {
public:
  DerivedTwiceNotBound() : Derived(), name_("DerivedTwiceNotBound") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class DerivedThrice : public DerivedTwiceNotBound {
public:
  DerivedThrice() : DerivedTwiceNotBound(), name_("DerivedThrice") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class DerivedFourTimesNotBound : public DerivedThrice {
public:
  DerivedFourTimesNotBound() : DerivedThrice(), name_("DerivedFourTimesNotBound") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyBase {
public:
  PolyBase(const std::string& s) : str_(s), name_("PolyBase") {}

  PolyBase() : name_("PolyBase") {}

  virtual ~PolyBase() {
  }

  virtual std::string virtualGetClassName() const {
    return name_;
  }

  std::string getClassName() const {
    return name_;
  }

private:
  std::string str_;
  std::string name_;
};

class PolySecondBase {
public:
  PolySecondBase() : name_("PolySecondBase") {}

  virtual ~PolySecondBase() {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDerived : public PolyBase {
public:
  PolyDerived() : PolyBase("PolyDerived"), name_("PolyDerived") {}

  std::string virtualGetClassName() const {
    return name_;
  }

  std::string getClassName() const {
    return name_;
  }

  static void setPtrDerived() {
    ptr_ = std::shared_ptr<PolyDerived>(new PolyDerived());
  }

  static void releasePtr() {
    ptr_.reset();
  }

  static std::string getPtrClassName() {
    return ptr_->getClassName();
  }

  static std::shared_ptr<PolyBase> getPtr() {
    return ptr_;
  }

private:
  std::string name_;
  static std::shared_ptr<PolyBase> ptr_;
};
std::shared_ptr<PolyBase> PolyDerived::ptr_;

class PolySiblingDerived : public PolyBase {
public:
  PolySiblingDerived() : PolyBase(), name_("PolySiblingDerived") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyMultiplyDerived : public PolyBase, public PolySecondBase {
public:
  PolyMultiplyDerived() : PolyBase(), PolySecondBase(), name_("PolyMultiplyDerived") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDerivedTwiceWithoutSmartPointer : public PolyDerived {
public:
  PolyDerivedTwiceWithoutSmartPointer() : PolyDerived(), name_("PolyDerivedTwiceWithoutSmartPointer") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDerivedTwiceNotBound : public PolyDerived {
public:
  PolyDerivedTwiceNotBound() : PolyDerived(), name_("PolyDerivedTwiceNotBound") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDerivedThrice : public PolyDerivedTwiceNotBound {
public:
  PolyDerivedThrice() : PolyDerivedTwiceNotBound(), name_("PolyDerivedThrice") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDerivedFourTimesNotBound : public PolyDerivedThrice {
public:
  PolyDerivedFourTimesNotBound() : PolyDerivedThrice(), name_("PolyDerivedFourTimesNotBound") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDiamondBase {
public:
  PolyDiamondBase() : name_("PolyBase") {}
  ~PolyDiamondBase() {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDiamondDerived : public PolyDiamondBase {
public:
  PolyDiamondDerived() : PolyDiamondBase(), name_("PolyDiamondDerived") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDiamondSiblingDerived : public PolyDiamondBase {
public:
  PolyDiamondSiblingDerived() : PolyDiamondBase(), name_("PolyDiamondSiblingDerived") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

class PolyDiamondMultiplyDerived : public PolyDiamondDerived,
                                   public PolyDiamondSiblingDerived {
public:
  PolyDiamondMultiplyDerived()
    : PolyDiamondDerived(), PolyDiamondSiblingDerived(),
      name_("PolyDiamondMultiplyDerived") {}

  std::string getClassName() const {
    return name_;
  }

private:
  std::string name_;
};

// End Inheritance Hierarchy Class Definitions

std::map<std::string, int> embind_test_get_string_int_map() {
  std::map<std::string, int> m;

  m["one"] = 1;
  m["two"] = 2;

  return m;
};

struct Vector {
  Vector() = delete;

  Vector(float x_, float y_, float z_, float w_) : x(x_), y(y_), z(z_), w(w_) {}

  float x, y, z, w;

  float& operator[](int i) {
    return (&x)[i];
  }

  const float& operator[](int i) const {
    return (&x)[i];
  }

  float getY() const {
    return y;
  }
  void setY(float _y) {
    y = _y;
  }
};

struct DummyDataToTestPointerAdjustment {
  std::string dummy;
};

struct TupleVector : DummyDataToTestPointerAdjustment, Vector {
  TupleVector() : Vector(0, 0, 0, 0) {}
  TupleVector(float x, float y, float z, float w) : Vector(x, y, z, w) {}
};

struct StructVector : DummyDataToTestPointerAdjustment, Vector {
  StructVector() : Vector(0, 0, 0, 0) {}
  StructVector(float x, float y, float z, float w) : Vector(x, y, z, w) {}
};

float readVectorZ(const Vector& v) {
  return v.z;
}

void writeVectorZ(Vector& v, float z) {
  v.z = z;
}

struct TupleVectorTuple {
  TupleVector v = TupleVector(0, 0, 0, 0);
};

TupleVector emval_test_return_TupleVector() {
  return TupleVector(1, 2, 3, 4);
}

TupleVector emval_test_take_and_return_TupleVector(TupleVector v) {
  return v;
}

TupleVectorTuple emval_test_return_TupleVectorTuple() {
  TupleVectorTuple cvt;
  cvt.v = emval_test_return_TupleVector();
  return cvt;
}

StructVector emval_test_return_StructVector() {
  return StructVector(1, 2, 3, 4);
}

StructVector emval_test_take_and_return_StructVector(StructVector v) {
  return v;
}

struct CustomStruct {
  CustomStruct() : field(10) {
  }

  const int& getField() const {
    return field;
  }

  int field;
};

struct TupleInStruct {
  TupleVector field;
};

TupleInStruct emval_test_take_and_return_TupleInStruct(TupleInStruct cs) {
  return cs;
}

struct NestedStruct {
  int x;
  int y;
};
struct ArrayInStruct {
  int field1[2];
  NestedStruct field2[2];
};
ArrayInStruct emval_test_take_and_return_ArrayInStruct(ArrayInStruct cs) {
  return cs;
}

enum Enum { ONE, TWO };

Enum emval_test_take_and_return_Enum(Enum e) {
  return e;
}

enum class EnumClass : char { ONE, TWO };

EnumClass emval_test_take_and_return_EnumClass(EnumClass e) {
  return e;
}

void emval_test_call_function(val v, int i, float f, TupleVector tv, StructVector sv) {
  v(i, f, tv, sv);
}

struct CharWrapper {
  CharWrapper(char v) {
    value = v;
  };

  char getValue() {
    return value;
  }

  char value;
};

class UniquePtrToConstructor {
public:
  UniquePtrToConstructor(std::unique_ptr<CharWrapper> p)
    : value((*p).getValue()) {
  }

  char getValue() const {
    return value;
  }

private:
  char value;
};

std::unique_ptr<CharWrapper> embind_test_return_unique_ptr(char v) {
  return std::unique_ptr<CharWrapper>(new CharWrapper(v));
}

UniquePtrToConstructor* embind_test_construct_class_with_unique_ptr(char v) {
  return new UniquePtrToConstructor(embind_test_return_unique_ptr(v));
}

char embind_test_accept_unique_ptr(std::unique_ptr<CharWrapper> p) {
  return (*p.get()).getValue();
}

std::unique_ptr<ValHolder> emval_test_return_unique_ptr() {
  return std::unique_ptr<ValHolder>(new ValHolder(val::object()));
}

class UniquePtrLifetimeMock {
public:
  UniquePtrLifetimeMock(val l) : logger(l) {
    logger(std::string("(constructor)"));
  }
  ~UniquePtrLifetimeMock() {
    logger(std::string("(destructor)"));
  }

private:
  val logger;
};

std::unique_ptr<UniquePtrLifetimeMock> emval_test_return_unique_ptr_lifetime(val logger) {
  return std::unique_ptr<UniquePtrLifetimeMock>(new UniquePtrLifetimeMock(logger));
}

std::shared_ptr<ValHolder> emval_test_return_shared_ptr() {
  return std::shared_ptr<ValHolder>(new ValHolder(val::object()));
}

std::shared_ptr<ValHolder> emval_test_return_empty_shared_ptr() {
  return std::shared_ptr<ValHolder>();
}

bool emval_test_is_shared_ptr_null(std::shared_ptr<ValHolder> p) {
  return !p;
}

static SmallClass smallClass;
static BigClass bigClass;

SmallClass embind_test_return_small_class_instance() {
  return smallClass;
}

BigClass embind_test_return_big_class_instance() {
  return bigClass;
}

int embind_test_accept_small_class_instance(SmallClass c) {
  return c.member;
}

int embind_test_accept_big_class_instance(BigClass c) {
  return c.member;
}

// Begin Inheritance Hierarchy Test Wrappers

Base* embind_test_return_raw_base_ptr() {
  return new Base();
}

Base* embind_test_return_raw_derived_ptr_as_base() {
  return new Derived();
}

Base* embind_test_return_raw_sibling_derived_ptr_as_base() {
  return new SiblingDerived();
}

PolyBase* embind_test_return_raw_polymorphic_derived_ptr_as_base() {
  return new PolyDerived();
}

PolyBase* embind_test_return_raw_polymorphic_sibling_derived_ptr_as_base() {
  return new PolySiblingDerived();
}

PolyBase* embind_test_return_raw_polymorphic_multiply_derived_ptr_as_base() {
  return new PolyMultiplyDerived();
}

PolySecondBase* embind_test_return_raw_polymorphic_multiply_derived_ptr_as_second_base() {
  return new PolyMultiplyDerived();
}

PolyBase* embind_test_return_raw_polymorphic_derived_four_times_not_bound_as_base() {
  return new PolyDerivedFourTimesNotBound();
}

std::shared_ptr<Base> embind_test_return_smart_base_ptr() {
  return std::shared_ptr<Base>(new Base());
}

std::shared_ptr<PolyBase> embind_test_return_smart_polymorphic_base_ptr() {
  return std::shared_ptr<PolyBase>(new PolyBase("PolyBase"));
}

std::shared_ptr<Derived> embind_test_return_smart_derived_ptr() {
  return std::shared_ptr<Derived>(new Derived());
}

std::shared_ptr<SiblingDerived> embind_test_return_smart_sibling_derived_ptr() {
  return std::shared_ptr<SiblingDerived>(new SiblingDerived());
}

std::shared_ptr<MultiplyDerived> embind_test_return_smart_multiply_derived_ptr() {
  return std::shared_ptr<MultiplyDerived>(new MultiplyDerived());
}

std::shared_ptr<DerivedThrice> embind_test_return_smart_derived_thrice_ptr() {
  return std::shared_ptr<DerivedThrice>(new DerivedThrice());
}

std::shared_ptr<PolyDerived> embind_test_return_smart_polymorphic_derived_ptr() {
  return std::shared_ptr<PolyDerived>(new PolyDerived());
}

std::shared_ptr<PolySiblingDerived> embind_test_return_smart_polymorphic_sibling_derived_ptr() {
  return std::shared_ptr<PolySiblingDerived>(new PolySiblingDerived());
}

std::shared_ptr<PolyMultiplyDerived> embind_test_return_smart_polymorphic_multiply_derived_ptr() {
  return std::shared_ptr<PolyMultiplyDerived>(new PolyMultiplyDerived());
}

std::shared_ptr<PolyBase> embind_test_return_poly_derived_twice_without_smart_pointer_as_poly_base() {
  return std::shared_ptr<PolyBase>(new PolyDerivedTwiceWithoutSmartPointer());
}

std::shared_ptr<PolyDerivedThrice> embind_test_return_smart_poly_derived_thrice_ptr() {
  return std::shared_ptr<PolyDerivedThrice>(new PolyDerivedThrice());
}

std::shared_ptr<PolyBase> embind_test_return_smart_derived_ptr_as_base() {
  return std::shared_ptr<PolyBase>(new PolyDerived());
}

val embind_test_return_smart_derived_ptr_as_val() {
  return val(std::shared_ptr<PolyBase>(new PolyDerived()));
}

std::shared_ptr<PolyBase> embind_test_return_smart_polymorphic_derived_ptr_as_base() {
  return std::shared_ptr<PolyBase>(new PolyDerived());
}

std::shared_ptr<PolyBase> embind_test_return_smart_polymorphic_sibling_derived_ptr_as_base() {
  return std::shared_ptr<PolyBase>(new PolySiblingDerived());
}

std::string embind_test_get_class_name_via_base_ptr(Base* p) {
  return p->getClassName();
}

std::string embind_test_get_class_name_via_second_base_ptr(SecondBase* p) {
  return p->getClassName();
}

std::string embind_test_get_class_name_via_polymorphic_base_ptr(PolyBase* p) {
  return p->getClassName();
}

std::string embind_test_get_class_name_via_polymorphic_second_base_ptr(PolySecondBase* p) {
  return p->getClassName();
}

std::string embind_test_get_class_name_via_smart_base_ptr(std::shared_ptr<Base> p) {
  return p->getClassName();
}

std::string embind_test_get_class_name_via_reference_to_smart_base_ptr(std::shared_ptr<Base>& p) {
  return p->getClassName();
}

std::string embind_test_get_class_name_via_smart_second_base_ptr(std::shared_ptr<SecondBase> p) {
  return p->getClassName();
}

std::string embind_test_get_class_name_via_smart_polymorphic_base_ptr(std::shared_ptr<PolyBase> p) {
  return p->getClassName();
}

std::string embind_test_get_virtual_class_name_via_smart_polymorphic_base_ptr(std::shared_ptr<PolyBase> p) {
  return p->virtualGetClassName();
}

std::string embind_test_get_class_name_via_smart_polymorphic_second_base_ptr(std::shared_ptr<PolySecondBase> p) {
  return p->getClassName();
}

void embind_modify_smart_pointer_passed_by_reference(std::shared_ptr<Base>& p) {
  p = std::shared_ptr<Base>(new Base());
  p->name = "Changed";
}

void embind_attempt_to_modify_smart_pointer_when_passed_by_value(std::shared_ptr<Base> p) {
  p = std::shared_ptr<Base>(new Base());
  p->name = "Changed";
}

static std::shared_ptr<Base> savedBasePointer;

void embind_save_smart_base_pointer(std::shared_ptr<Base> p) {
  savedBasePointer = p;
}

// End Inheritance Hierarchy Test Wrappers

std::vector<int> emval_test_return_vector() {
  int myints[] = {10, 20, 30};
  return std::vector<int>(myints, myints + sizeof(myints) / sizeof(int));
}

std::vector<std::vector<int>> emval_test_return_vector_of_vectors() {
  int myints1[] = {10, 20, 30};
  int myints2[] = {40, 50, 60};
  std::vector<int> vec1(myints1, myints1 + sizeof(myints1) / sizeof(int));
  std::vector<int> vec2(myints2, myints2 + sizeof(myints2) / sizeof(int));
  std::vector<std::vector<int>> vec3;
  vec3.emplace_back(vec1);
  vec3.emplace_back(vec2);
  return vec3;
}

std::vector<std::shared_ptr<StringHolder>> emval_test_return_shared_ptr_vector() {
  std::vector<std::shared_ptr<StringHolder>> sharedStrVector;
  sharedStrVector.push_back(std::shared_ptr<StringHolder>(new StringHolder("string #1")));
  sharedStrVector.push_back(std::shared_ptr<StringHolder>(new StringHolder("string #2")));

  return sharedStrVector;
}

std::vector<SmallClass*> emval_test_return_vector_pointers() {
  std::vector<SmallClass*> vec;
  vec.push_back(new SmallClass());
  return vec;
}

void test_string_with_vec(const std::string& p1, std::vector<std::string>& v1) {
  // THIS DOES NOT WORK -- need to get as val and then call vecFromJSArray
  printf("%s\n", p1.c_str());
}

#if __cplusplus >= 201703L
std::optional<int> embind_test_return_optional_int(bool create) {
  if (create) {
    return 42;
  }
  return {};
}
std::optional<float> embind_test_return_optional_float(bool create) {
  if (create) {
    return 4.2;
  }
  return {};
}
std::optional<std::string> embind_test_return_optional_string(bool create) {
  if (create) {
    return "hello";
  }
  return {};
}
std::optional<SmallClass> embind_test_return_optional_small_class(bool create) {
  if (create) {
    return SmallClass();
  }
  return {};
}
std::optional<SmallClass*>
embind_test_return_optional_small_class_pointer(bool create) {
  if (create) {
    return new SmallClass();
  }
  return {};
}

int embind_test_optional_int_arg(std::optional<int> arg) {
  if (arg) {
    return *arg;
  }
  return -1;
}
float embind_test_optional_float_arg(std::optional<float> arg) {
  if (arg) {
    return *arg;
  }
  return -1.1;
}
std::string embind_test_optional_string_arg(std::optional<std::string> arg) {
  if (arg) {
    return *arg;
  }
  return "no value";
}

int embind_test_optional_small_class_arg(std::optional<SmallClass> arg) {
  if (arg) {
    return arg->member;
  }
  return -1;
}
void embind_test_optional_multiple_arg(int arg1,
                                       std::optional<int> arg2,
                                       std::optional<int> arg3) {
}
#endif

val embind_test_getglobal() {
  return val::global();
}

val embind_test_new_Object() {
  return val::global("Object").new_();
}

val embind_test_new_factory(val factory, val argument) {
  return factory.new_(10, std::string("hello"), argument);
}

class AbstractClass {
public:
  virtual ~AbstractClass() {}
  virtual std::string abstractMethod() const = 0;
  virtual std::string optionalMethod(std::string s) const {
    return "optional" + s;
  }

  virtual std::shared_ptr<Derived> returnsSharedPtr() = 0;
  virtual void differentArguments(
    int i, double d, unsigned char f, double q, std::string) = 0;

  std::string concreteMethod() const {
    return "concrete";
  }

  virtual void passShared(const std::shared_ptr<Derived>&) {
  }

  virtual void passVal(const val& v) {
  }
};

EMSCRIPTEN_SYMBOL(optionalMethod);

class AbstractClassWrapper : public wrapper<AbstractClass> {
public:
  EMSCRIPTEN_WRAPPER(AbstractClassWrapper);

  std::string abstractMethod() const override {
    return call<std::string>("abstractMethod");
  }

  std::string optionalMethod(std::string s) const override {
    return call<std::string>("optionalMethod", s);
  }

  std::shared_ptr<Derived> returnsSharedPtr() override {
    return call<std::shared_ptr<Derived>>("returnsSharedPtr");
  }

  void differentArguments(int i, double d, unsigned char f, double q, std::string s) override {
    return call<void>("differentArguments", i, d, f, q, s);
  }

  virtual void passShared(const std::shared_ptr<Derived>& p) override {
    return call<void>("passShared", p);
  }

  virtual void passVal(const val& v) override {
    return call<void>("passVal", v);
  }
};

class ConcreteClass : public AbstractClass {
  std::string abstractMethod() const {
    return "from concrete";
  }

  void differentArguments(
    int i, double d, unsigned char f, double q, std::string s) {
  }

  std::shared_ptr<Derived> returnsSharedPtr() {
    return std::shared_ptr<Derived>();
  }
};

std::shared_ptr<AbstractClass> getAbstractClass() {
  return std::make_shared<ConcreteClass>();
}

std::string callAbstractMethod(AbstractClass& ac) {
  return ac.abstractMethod();
}

std::string callOptionalMethod(AbstractClass& ac, std::string s) {
  return ac.optionalMethod(s);
}

void callReturnsSharedPtrMethod(AbstractClass& ac) {
  std::shared_ptr<Derived> sp = ac.returnsSharedPtr();
  // unused: sp
}

void callDifferentArguments(AbstractClass& ac,
                            int i,
                            double d,
                            unsigned char f,
                            double q,
                            std::string s) {
  return ac.differentArguments(i, d, f, q, s);
}

struct AbstractClassWithConstructor {
  explicit AbstractClassWithConstructor(std::string s) : s(s) {}

  virtual ~AbstractClassWithConstructor(){};

  virtual std::string abstractMethod() = 0;
  std::string concreteMethod() {
    return s;
  }

  std::string s;
};

struct AbstractClassWithConstructorWrapper
  : public wrapper<AbstractClassWithConstructor> {
  EMSCRIPTEN_WRAPPER(AbstractClassWithConstructorWrapper);

  virtual std::string abstractMethod() override {
    return call<std::string>("abstractMethod");
  }
};

std::string callAbstractMethod2(AbstractClassWithConstructor& ac) {
  return ac.abstractMethod();
}

struct HeldAbstractClass : public PolyBase, public PolySecondBase {
  virtual void method() = 0;
};
struct HeldAbstractClassWrapper : wrapper<HeldAbstractClass> {
  EMSCRIPTEN_WRAPPER(HeldAbstractClassWrapper);

  virtual void method() override {
    return call<void>("method");
  }
};

std::shared_ptr<PolySecondBase> passHeldAbstractClass(std::shared_ptr<HeldAbstractClass> p) {
  return p;
}

void passShared(AbstractClass& ac) {
  auto p = std::make_shared<Derived>();
  ac.passShared(p);
}

void passVal(AbstractClass& ac, val v) {
  return ac.passVal(v);
}

EMSCRIPTEN_BINDINGS(interface_tests) {
  class_<AbstractClass>("AbstractClass")
    .smart_ptr<std::shared_ptr<AbstractClass>>("shared_ptr<AbstractClass>")
    .allow_subclass<AbstractClassWrapper>("AbstractClassWrapper")
    .function("abstractMethod", &AbstractClass::abstractMethod, pure_virtual())
    // The optional_override is necessary because, otherwise, the C++ compiler
    // cannot deduce the signature of the lambda function.
    .function("optionalMethod", optional_override(
      [](AbstractClass& this_, const std::string& s) {
        return this_.AbstractClass::optionalMethod(s);
      }
    ))
    .function("concreteMethod", &AbstractClass::concreteMethod)
    .function("passShared", select_overload<void(AbstractClass&, const std::shared_ptr<Derived>&)>([](AbstractClass& self, const std::shared_ptr<Derived>& derived) {
      self.AbstractClass::passShared(derived);
    }))
    .function("passVal", select_overload<void(AbstractClass&, const val&)>([](AbstractClass& self, const val& v) {
      self.AbstractClass::passVal(v);
    }))
    ;

  function("getAbstractClass", &getAbstractClass);
  function("callAbstractMethod", &callAbstractMethod);
  function("callOptionalMethod", &callOptionalMethod);
  function("callReturnsSharedPtrMethod", &callReturnsSharedPtrMethod);
  function("callDifferentArguments", &callDifferentArguments);
  function("passShared", &passShared);
  function("passVal", &passVal);

  class_<AbstractClassWithConstructor>("AbstractClassWithConstructor")
    .allow_subclass<AbstractClassWithConstructorWrapper>("AbstractClassWithConstructorWrapper", constructor<std::string>())
    .function("abstractMethod", &AbstractClassWithConstructor::abstractMethod, pure_virtual())
    .function("concreteMethod", &AbstractClassWithConstructor::concreteMethod)
    ;
  function("callAbstractMethod2", &callAbstractMethod2);

  class_<HeldAbstractClass, base<PolySecondBase>>("HeldAbstractClass")
    .smart_ptr<std::shared_ptr<HeldAbstractClass>>("shared_ptr<HeldAbstractClass>")
    .allow_subclass<HeldAbstractClassWrapper, std::shared_ptr<HeldAbstractClassWrapper>>("HeldAbstractClassWrapper", "HeldAbstractClassWrapperPtr")
    .function("method", &HeldAbstractClass::method, pure_virtual())
    ;
  function("passHeldAbstractClass", &passHeldAbstractClass);
}

template<typename T, size_t sizeOfArray>
constexpr size_t getElementCount(T (&)[sizeOfArray]) {
  return sizeOfArray;
}

static void callWithMemoryView(val v) {
  // static so the JS test can read the memory after callTakeMemoryView runs
  static unsigned char data[] = {0, 1, 2, 3, 4, 5, 6, 7};
  v(typed_memory_view(getElementCount(data), data));
  static float f[] = {1.5f, 2.5f, 3.5f, 4.5f};
  v(typed_memory_view(getElementCount(f), f));
  static short s[] = {1000, 100, 10, 1};
  v(typed_memory_view(getElementCount(s), s));
}

EMSCRIPTEN_BINDINGS(memory_view_tests) {
  function("callWithMemoryView", &callWithMemoryView);
}

class HasExternalConstructor {
public:
  HasExternalConstructor(const std::string& str) : m(str) {}

  std::string getString() const {
    return m;
  }

  std::string m;
};

HasExternalConstructor* createHasExternalConstructor(const std::string& str) {
  return new HasExternalConstructor(str);
}

class HasExternalConstructorNoCopy {
private:
  HasExternalConstructorNoCopy(int i) : m(i) {
  }
  int m;

public:
  HasExternalConstructorNoCopy(HasExternalConstructorNoCopy&) = delete;
  HasExternalConstructorNoCopy(HasExternalConstructorNoCopy&&) = default;
  int getInt() {
    return m;
  }
  static HasExternalConstructorNoCopy create(int i) {
    HasExternalConstructorNoCopy obj(i);
    return obj;
  }
};

template<typename T> class CustomSmartPtr {
public:
  CustomSmartPtr() : CustomSmartPtr(nullptr) {
    std::fill(d_, d_ + N_, Valid);
  }

  explicit CustomSmartPtr(T* t) : ptr_(t) {
    std::fill(d_, d_ + N_, Valid);
  }

  CustomSmartPtr(const CustomSmartPtr& other) : ptr_(other.ptr_) {
    other.verify();
    std::fill(d_, d_ + N_, Valid);
    if (ptr_) {
      ++(ptr_->refcount);
    }
  }

  ~CustomSmartPtr() {
    verify();
    std::fill(d_, d_ + N_, Deleted);

    if (ptr_ && --ptr_->refcount == 0) {
      delete ptr_;
    }
  }

  T* get_raw() const {
    return ptr_;
  }

private:
  void verify() const {
    for (size_t i = 0; i < N_; ++i) {
      if (d_[i] != Valid) {
        abort();
      }
    }
  }

  enum {
    Valid = 255,
    Deleted = 127,
  };
  static constexpr size_t N_ = 1000000;
  unsigned char d_[N_];
  T* ptr_;

  CustomSmartPtr& operator=(const CustomSmartPtr&) = delete;
};

class HeldBySmartPtr {
public:
  HeldBySmartPtr(int i, const std::string& s) : i(i), s(s) {
  }

  static CustomSmartPtr<HeldBySmartPtr> newCustomPtr(int i,
                                                     const std::string& s) {
    return CustomSmartPtr<HeldBySmartPtr>(new HeldBySmartPtr(i, s));
  }

  int refcount = 1;
  int i;
  std::string s;
};

HeldBySmartPtr takesHeldBySmartPtr(HeldBySmartPtr p) {
  return p;
}
std::shared_ptr<HeldBySmartPtr>
takesHeldBySmartPtrSharedPtr(std::shared_ptr<HeldBySmartPtr> p) {
  return p;
}

namespace emscripten {
template<typename T> struct smart_ptr_trait<CustomSmartPtr<T>> {
  typedef CustomSmartPtr<T> pointer_type;
  typedef T element_type;

  static sharing_policy get_sharing_policy() {
    return sharing_policy::NONE;
  }

  static T* get(const CustomSmartPtr<T>& p) {
    return p.get_raw();
  }

  static CustomSmartPtr<T> share(const CustomSmartPtr<T>& r, T* ptr) {
    ++ptr->refcount; // implement an adopt API?
    return CustomSmartPtr<T>(ptr);
  }

  static pointer_type* construct_null() {
    return new pointer_type;
  }
};
} // namespace emscripten

typedef CustomSmartPtr<class HeldByCustomSmartPtr> HeldByCustomSmartPtrPtr;

class HeldByCustomSmartPtr {
public:
  HeldByCustomSmartPtr(int i, const std::string& s) : i(i), s(s) {}

  static HeldByCustomSmartPtrPtr create(int i, const std::string& s) {
    return HeldByCustomSmartPtrPtr(new HeldByCustomSmartPtr(i, s));
  }

  static std::shared_ptr<HeldByCustomSmartPtr>
  createSharedPtr(int i, const std::string& s) {
    return std::make_shared<HeldByCustomSmartPtr>(i, s);
  };

  int refcount = 1;
  int i;
  std::string s;
};

HeldByCustomSmartPtr* passThroughRawPtr(HeldByCustomSmartPtr* p) {
  return p;
}
HeldByCustomSmartPtrPtr passThroughCustomSmartPtr(HeldByCustomSmartPtrPtr p) {
  return p;
}

struct Base1 {
public:
  Base1() : field1("Base1") {}
  std::string field1;

  std::string getField() const {
    return field1;
  }
};

struct Base2 {
public:
  Base2() : field2("Base2") {}
  std::string field2;

  std::string getField() const {
    return field2;
  }
};

struct HasTwoBases : public Base1, public Base2 {};

val get_module_property(const std::string& s) {
  return val::module_property(s.c_str());
}

std::string char_to_string(char ch) {
  char str[256];
  sprintf(str, "%d", (int)ch);
  return str;
}

std::string signed_char_to_string(signed char ch) {
  char str[256];
  sprintf(str, "%hhd", ch);
  return str;
}

std::string unsigned_char_to_string(unsigned char ch) {
  char str[256];
  sprintf(str, "%hhu", ch);
  return str;
}

std::string short_to_string(short val) {
  char str[256];
  sprintf(str, "%hd", val);
  return str;
}

std::string unsigned_short_to_string(unsigned short val) {
  char str[256];
  sprintf(str, "%hu", val);
  return str;
}

std::string int_to_string(int val) {
  char str[256];
  sprintf(str, "%d", val);
  return str;
}

std::string unsigned_int_to_string(unsigned int val) {
  char str[256];
  sprintf(str, "%u", val);
  return str;
}

std::string long_to_string(long val) {
  char str[256];
  sprintf(str, "%ld", val);
  return str;
}

std::string unsigned_long_to_string(unsigned long val) {
  char str[256];
  sprintf(str, "%lu", val);
  return str;
}

// test loading unsigned value from memory
static unsigned char uchar;
void store_unsigned_char(unsigned char arg) {
  uchar = arg;
}

unsigned char load_unsigned_char() {
  return uchar;
}

static unsigned short ushort;
void store_unsigned_short(unsigned short arg) {
  ushort = arg;
}

unsigned short load_unsigned_short() {
  return ushort;
}

static unsigned int uint;
void store_unsigned_int(unsigned int arg) {
  uint = arg;
}

unsigned int load_unsigned_int() {
  return uint;
}

static unsigned long ulong;
void store_unsigned_long(unsigned long arg) {
  ulong = arg;
}

unsigned long load_unsigned_long() {
  return ulong;
}

template<int I> class ConstructFromFunctor {
public:
  ConstructFromFunctor(const val& v, int a) : v_(v), a_(a) {
  }

  val getVal() const {
    return v_;
  }

  int getA() const {
    return a_;
  }

private:
  val v_;
  int a_;
};

template<int I>
ConstructFromFunctor<I> construct_from_functor_mixin(const val& v, int i) {
  return {v, i};
}

EMSCRIPTEN_BINDINGS(tests) {
  register_vector<int>("IntegerVector");
  register_vector<char>("CharVector");
  register_vector<unsigned>("VectorUnsigned");
  register_vector<unsigned char>("VectorUnsignedChar");
  register_vector<std::string>("StringVector");
  register_vector<emscripten::val>("EmValVector");
  register_vector<float>("FloatVector");
  register_vector<std::vector<int>>("IntegerVectorVector");
  register_vector<SmallClass*>("SmallClassPointerVector");

  class_<DummyForPointer>("DummyForPointer");

  function("mallinfo", &emval_test_mallinfo);
  function("emval_test_new_integer", &emval_test_new_integer);
  function("emval_test_new_string", &emval_test_new_string);
  function("emval_test_get_string_from_val", &emval_test_get_string_from_val);
  function("emval_test_new_object", &emval_test_new_object);
  function("emval_test_instance_pointer", &emval_test_instance_pointer);
  function("emval_test_value_from_instance_pointer", &emval_test_value_from_instance_pointer);
  function("emval_test_passthrough_unsigned", &emval_test_passthrough_unsigned);
  function("emval_test_passthrough", &emval_test_passthrough);
  function("emval_test_return_void", &emval_test_return_void);
  function("emval_test_not", &emval_test_not);

  function("emval_test_is_true", &emval_test_is_true);
  function("emval_test_is_false", &emval_test_is_false);
  function("emval_test_is_null", &emval_test_is_null);
  function("emval_test_is_undefined", &emval_test_is_undefined);
  function("emval_test_equals", &emval_test_equals);
  function("emval_test_strictly_equals", &emval_test_strictly_equals);

  function("emval_test_as_unsigned", &emval_test_as_unsigned);
  function("emval_test_get_length", &emval_test_get_length);
  function("emval_test_add", &emval_test_add);
  function("const_ref_adder", &const_ref_adder);
  function("emval_test_sum", &emval_test_sum);

  class_<DestructorCounter>("DestructorCounter");
  function("emval_test_callback_arg_lifetime", &emval_test_callback_arg_lifetime);

  function("get_non_ascii_string", &get_non_ascii_string);
  function("get_non_ascii_wstring", &get_non_ascii_wstring);
  function("get_literal_wstring", &get_literal_wstring);
  function("force_memory_growth", &force_memory_growth);

  //function("emval_test_take_and_return_const_char_star", &emval_test_take_and_return_const_char_star);
  function("emval_test_take_and_return_std_string", &emval_test_take_and_return_std_string);
  function("emval_test_take_and_return_std_string_const_ref", &emval_test_take_and_return_std_string_const_ref);
  function("take_and_return_std_wstring", &take_and_return_std_wstring);
  function("take_and_return_std_u16string", &take_and_return_std_u16string);
  function("take_and_return_std_u32string", &take_and_return_std_u32string);
  function("get_non_ascii_u16string", &get_non_ascii_u16string);
  function("get_non_ascii_u32string", &get_non_ascii_u32string);
  function("get_literal_u16string", &get_literal_u16string);
  function("get_literal_u32string", &get_literal_u32string);

  //function("emval_test_take_and_return_CustomStruct", &emval_test_take_and_return_CustomStruct);

  value_array<TupleVector>("TupleVector")
    .element(&TupleVector::x)
    .element(&Vector::getY, &Vector::setY)
    .element(&readVectorZ, &writeVectorZ)
    .element(emscripten::index<3>())
    ;

  function("emval_test_return_TupleVector", &emval_test_return_TupleVector);
  function("emval_test_take_and_return_TupleVector", &emval_test_take_and_return_TupleVector);

  value_array<TupleVectorTuple>("TupleVectorTuple")
    .element(&TupleVectorTuple::v)
    ;

  function("emval_test_return_TupleVectorTuple", &emval_test_return_TupleVectorTuple);

  value_object<StructVector>("StructVector")
    .field("x", &StructVector::x)
    .field("y", &Vector::getY, &Vector::setY)
    .field("z", &readVectorZ, &writeVectorZ)
    .field("w", emscripten::index<3>())
    ;

  function("emval_test_return_StructVector", &emval_test_return_StructVector);
  function("emval_test_take_and_return_StructVector", &emval_test_take_and_return_StructVector);

  value_object<TupleInStruct>("TupleInStruct")
    .field("field", &TupleInStruct::field)
    ;

  function("emval_test_take_and_return_TupleInStruct", &emval_test_take_and_return_TupleInStruct);


  value_array<std::array<int, 2>>("array_int_2")
    .element(emscripten::index<0>())
    .element(emscripten::index<1>())
    ;
  value_array<std::array<NestedStruct, 2>>("array_NestedStruct_2")
    .element(emscripten::index<0>())
    .element(emscripten::index<1>())
    ;
  value_object<NestedStruct>("NestedStruct")
    .field("x", &NestedStruct::x)
    .field("y", &NestedStruct::y)
    ;

  value_object<ArrayInStruct>("ArrayInStruct")
    .field("field1", &ArrayInStruct::field1)
    .field("field2", &ArrayInStruct::field2)
    ;
  function("emval_test_take_and_return_ArrayInStruct", &emval_test_take_and_return_ArrayInStruct);

  using namespace std::placeholders;

  class_<ConstructFromFunctor<1>>("ConstructFromStdFunction")
    .constructor(std::function<ConstructFromFunctor<1>(const val&, int)>(&construct_from_functor_mixin<1>))
    .function("getVal", &ConstructFromFunctor<1>::getVal)
    .function("getA", &ConstructFromFunctor<1>::getA)
    ;

  class_<ConstructFromFunctor<2>>("ConstructFromFunctionObject")
    .constructor<ConstructFromFunctor<2>(const val&, int)>(std::bind(&construct_from_functor_mixin<2>, _1, _2))
    .function("getVal", &ConstructFromFunctor<2>::getVal)
    .function("getA", &ConstructFromFunctor<2>::getA)
    ;

  class_<ValHolder>("ValHolder")
    .smart_ptr<std::shared_ptr<ValHolder>>("std::shared_ptr<ValHolder>")
    .constructor<val>()
    .function("getVal", &ValHolder::getVal)
    .function("getValNonConst", &ValHolder::getValNonConst)
    .function("getConstVal", &ValHolder::getConstVal)
    .function("getValConstRef", &ValHolder::getValConstRef)
    .function("setVal", &ValHolder::setVal)
    .function("getValFunction", std::function<val(const ValHolder&)>(&valholder_get_value_mixin))
    .function("setValFunction", std::function<void(ValHolder&, const val&)>(&valholder_set_value_mixin))
    .function<val(const ValHolder&)>("getValFunctor", std::bind(&valholder_get_value_mixin, _1))
    .function<void(ValHolder&, const val&)>("setValFunctor", std::bind(&valholder_set_value_mixin, _1, _2))
    .property("val", &ValHolder::getVal, &ValHolder::setVal)
    .property("val_readonly", &ValHolder::getVal)
    .property("readonly_function_val", std::function<val(const ValHolder&)>(&valholder_get_value_mixin))
    .property("function_val", std::function<val(const ValHolder&)>(&valholder_get_value_mixin),
                              std::function<void(ValHolder&, const val&)>(&valholder_set_value_mixin))
    .property<val>("readonly_functor_val", std::bind(&valholder_get_value_mixin, _1))
    .property<val>("functor_val", std::bind(&valholder_get_value_mixin, _1),
                                  std::bind(&valholder_set_value_mixin, _1, _2))
    .class_function("makeConst", &ValHolder::makeConst, allow_raw_pointer<ret_val>())
    .class_function("makeValHolder", &ValHolder::makeValHolder)
    .class_function("some_class_method", &ValHolder::some_class_method)
    .class_function("set_via_raw_pointer",
                    &ValHolder::set_via_raw_pointer,
                    allow_raw_pointer<arg<0>>())
    .class_function("get_via_raw_pointer",
                    &ValHolder::get_via_raw_pointer,
                    allow_raw_pointer<arg<0>>())
    .class_function("transfer_via_raw_pointer",
                    &ValHolder::transfer_via_raw_pointer,
                    allow_raw_pointers())

    // non-member method
    .function("setEmpty", &emval_test_set_ValHolder_to_empty_object)
    .function("getValNonMember", &ValHolder::getValNonMember)
    ;

  function("emval_test_return_ValHolder", &emval_test_return_ValHolder);
  function("emval_test_set_ValHolder_to_empty_object", &emval_test_set_ValHolder_to_empty_object);

  class_<std::function<std::string(std::string)>>("StringFunctorString")
    .constructor<>()
    .function("opcall", &std::function<std::string(std::string)>::operator())
    ;

  function("emval_test_get_function_ptr", &emval_test_get_function_ptr);
  function("emval_test_take_and_call_functor", &emval_test_take_and_call_functor);

  class_<StringHolder>("StringHolder")
    .smart_ptr<std::shared_ptr<StringHolder>>("shared_ptr<StringHolder>")
    .constructor<std::string>()
    .function("set", &StringHolder::set)
    .function("get", &StringHolder::get)
    .function("get_const_ref", &StringHolder::get_const_ref)
    ;

  class_<SharedPtrHolder>("SharedPtrHolder")
    .constructor<>()
    .function("get", &SharedPtrHolder::get)
    .function("set", &SharedPtrHolder::set)
    ;

  class_<SmallClass>("SmallClass")
    .constructor<>()
    .property("member", &SmallClass::member)
    ;

  class_<BigClass>("BigClass")
    .constructor<>()
    .property("member", &BigClass::member)
    .property("otherMember", &BigClass::otherMember)
    .property("yetAnotherMember", &BigClass::yetAnotherMember)
    .function("getMember", &BigClass::getMember)
    ;

  class_<ParentClass>("ParentClass")
    .constructor<>()
    .function("getBigClass", &ParentClass::getBigClass)
    ;

  class_<TemplateClass<int>>("IntTemplateClass")
    .constructor<int, int, int>()
    .function("getMember", &TemplateClass<int>::getMember)
    ;

  class_<NoExceptClass>("NoExceptClass")
    .function("embind_test_no_except_function", &embind_test_no_except_function)
    .function("getValue", &NoExceptClass::getValue)
    .function("getValueConst", &NoExceptClass::getValueConst)
    .property("x", &NoExceptClass::getX, &NoExceptClass::setX);

  class_<ContainsTemplatedMemberClass>("ContainsTemplatedMemberClass")
    .constructor<>()
    .function("getTestTemplate", &ContainsTemplatedMemberClass::getTestTemplate)
    ;

  class_<SymbolNameClass>("SymbolNameClass")
    .constructor<>()
    .function("@@iterator", &SymbolNameClass::iterator)
    .class_function("@@species", &SymbolNameClass::species)
    ;

  // register Derived before Base as a test that it's possible to
  // register base classes afterwards
  class_<Derived, base<Base>>("Derived")
    .smart_ptr<std::shared_ptr<Derived>>("shared_ptr<Derived>")
    .constructor<>()
    .function("getClassName", &Derived::getClassName)
    .function("getMember", &Derived::getMember)
    .function("setMember", &Derived::setMember)
    .property("member", &Derived::member)
    .class_function("classFunction", &Derived::classFunction)
    ;

  class_<Base>("Base")
    .smart_ptr<std::shared_ptr<Base>>("shared_ptr<Base")
    .constructor<>()
    .function("getClassName", &Base::getClassName)
    .function("getClassNameFromBase", &Base::getClassNameFromBase)
    .function("getClassNameNotAvailableInDerivedClasses", &Base::getClassNameNotAvailableInDerivedClasses)
    .function("getMember", &Base::getMember)
    .function("setMember", &Base::setMember)
    .function("getBaseMember", &Base::getBaseMember)
    .function("setBaseMember", &Base::setBaseMember)
    .property("member", &Base::member)
    .property("baseMember", &Base::baseMember)
    .class_function("classFunction", &Base::classFunction)
    ;

  class_<SecondBase>("SecondBase")
    .smart_ptr<std::shared_ptr<SecondBase>>("shared_ptr<SecondBase>")
    .constructor<>()
    .function("getClassName", &SecondBase::getClassName)
    .function("getClassNameFromSecondBase", &SecondBase::getClassNameFromSecondBase)
    .function("getClassNameNotAvailableInDerivedClasses", &SecondBase::getClassNameNotAvailableInDerivedClasses)
    .function("getMember", &SecondBase::getMember)
    .function("setMember", &SecondBase::setMember)
    .function("getSecondBaseMember", &SecondBase::getSecondBaseMember)
    .function("setSecondBaseMember", &SecondBase::setSecondBaseMember)
    .property("member", &SecondBase::member)
    .property("secondBaseMember", &SecondBase::secondBaseMember)
    ;


  class_<DerivedHolder>("DerivedHolder")
    .constructor<>()
    .function("newDerived", &DerivedHolder::newDerived)
    .function("deleteDerived", &DerivedHolder::deleteDerived)
    .function("getDerived", &DerivedHolder::getDerived)
    .function("getDerivedClassName", &DerivedHolder::getDerivedClassName)
    ;

  class_<SiblingDerived>("SiblingDerived")
    .smart_ptr<std::shared_ptr<SiblingDerived>>("shared_ptr<SiblingDerived>")
    .constructor<>()
    .function("getClassName", &SiblingDerived::getClassName)
    ;

  class_<MultiplyDerived, base<Base>>("MultiplyDerived")
    .smart_ptr<std::shared_ptr<MultiplyDerived>>("shared_ptr<MultiplyDerived>")
    .constructor<>()
    .function("getClassName", &MultiplyDerived::getClassName)
    .class_function("getInstanceCount", &MultiplyDerived::getInstanceCount)
    ;

  class_<DerivedTwice, base<Derived> >("DerivedTwice")
    .constructor<>()
    .function("getClassName", &DerivedTwice::getClassName)
    ;

  class_<DerivedThrice, base<Derived> >("DerivedThrice")
    .smart_ptr<std::shared_ptr<DerivedThrice>>("shared_ptr<DerivedThrice>")
    .constructor<>()
    .function("getClassName", &DerivedThrice::getClassName)
    ;

  class_<PolyBase>("PolyBase")
    .smart_ptr<std::shared_ptr<PolyBase>>("shared_ptr<PolyBase>")
    .constructor<>()
    .function("virtualGetClassName", &PolyBase::virtualGetClassName)
    .function("getClassName", &PolyBase::getClassName)
    ;

  class_<PolySecondBase>("PolySecondBase")
    .smart_ptr<std::shared_ptr<PolySecondBase>>("shared_ptr<PolySecondBase>")
    .constructor<>()
    .function("getClassName", &PolySecondBase::getClassName)
    ;

  class_<PolyDerived, base<PolyBase>>("PolyDerived")
    .smart_ptr<std::shared_ptr<PolyDerived>>("shared_ptr<PolyDerived>")
    .constructor<>()
    .function("virtualGetClassName", &PolyDerived::virtualGetClassName)
    .function("getClassName", &PolyDerived::getClassName)
    .class_function("setPtrDerived", &PolyDerived::setPtrDerived)
    .class_function("releasePtr", &PolyDerived::releasePtr)
    .class_function("getPtrClassName", &PolyDerived::getPtrClassName)
    .class_function("getPtr", &PolyDerived::getPtr)
    ;
//    static void setPtrDerived() {
//        ptr = std::shared_ptr<PolyDerived>(new PolyDerived());
//    }
//
//    static std::string getPtrClassName() {
//        return ptr->getClassName();
//    }
//
//    static std::shared_ptr<PolyBase> getPtr() {
//        return ptr;
//    }

  class_<PolySiblingDerived, base<PolyBase>>("PolySiblingDerived")
    .smart_ptr<std::shared_ptr<PolySiblingDerived>>("shared_ptr<PolySiblingDerived>")
    .constructor<>()
    .function("getClassName", &PolySiblingDerived::getClassName)
    ;

  class_<PolyMultiplyDerived, base<PolyBase>>("PolyMultiplyDerived")
    .smart_ptr<std::shared_ptr<PolyMultiplyDerived>>("shared_ptr<PolyMultiplyDerived>")
    .constructor<>()
    .function("getClassName", &PolyMultiplyDerived::getClassName)
    ;

  class_<PolyDerivedThrice, base<PolyDerived>>("PolyDerivedThrice")
    .smart_ptr<std::shared_ptr<PolyDerivedThrice>>("shared_ptr<PolyDerivedThrice>")
    .constructor<>()
    .function("getClassName", &PolyDerivedThrice::getClassName)
    ;

  class_<PolyDiamondBase>("PolyDiamondBase")
    .smart_ptr<std::shared_ptr<PolyDiamondBase>>("shared_ptr<PolyDiamondBase>")
    .constructor<>()
    .function("getClassName", &PolyDiamondBase::getClassName)
    ;

  class_<PolyDiamondDerived>("PolyDiamondDerived")
    .smart_ptr<std::shared_ptr<PolyDiamondDerived>>("shared_ptr<PolyDiamondDerived>")
    .constructor<>()
    .function("getClassName", &PolyDiamondDerived::getClassName)
    ;

  class_<PolyDiamondSiblingDerived>("PolyDiamondSiblingDerived")
    .smart_ptr<std::shared_ptr<PolyDiamondSiblingDerived>>("shared_ptr<PolyDiamondSiblingDerived>")
    .constructor<>()
    .function("getClassName", &PolyDiamondSiblingDerived::getClassName)
    ;

  class_<PolyDiamondMultiplyDerived>("PolyDiamondMultiplyDerived")
    .smart_ptr<std::shared_ptr<PolyDiamondMultiplyDerived>>("shared_ptr<PolyDiamondMultiplyDerived>")
    .constructor<>()
    .function("getClassName", &PolyDiamondMultiplyDerived::getClassName)
    ;

  function("embind_test_return_small_class_instance", &embind_test_return_small_class_instance);
  function("embind_test_return_big_class_instance", &embind_test_return_big_class_instance);
  function("embind_test_accept_small_class_instance", &embind_test_accept_small_class_instance);
  function("embind_test_accept_big_class_instance", &embind_test_accept_big_class_instance);

  class_<UniquePtrToConstructor>("UniquePtrToConstructor")
    .function("getValue", &UniquePtrToConstructor::getValue)
    ;
  class_<CharWrapper>("CharWrapper");

  function("embind_test_construct_class_with_unique_ptr", embind_test_construct_class_with_unique_ptr, allow_raw_pointer<ret_val>());
  function("embind_test_return_unique_ptr", embind_test_return_unique_ptr);
  function("embind_test_accept_unique_ptr", embind_test_accept_unique_ptr);

  function("embind_test_return_raw_base_ptr", embind_test_return_raw_base_ptr, allow_raw_pointer<ret_val>());
  function("embind_test_return_raw_derived_ptr_as_base", embind_test_return_raw_derived_ptr_as_base, allow_raw_pointer<ret_val>());
  function("embind_test_return_raw_sibling_derived_ptr_as_base", embind_test_return_raw_sibling_derived_ptr_as_base, allow_raw_pointer<ret_val>());
  function("embind_test_return_raw_polymorphic_derived_ptr_as_base", embind_test_return_raw_polymorphic_derived_ptr_as_base, allow_raw_pointer<ret_val>());
  function("embind_test_return_raw_polymorphic_sibling_derived_ptr_as_base", embind_test_return_raw_polymorphic_sibling_derived_ptr_as_base, allow_raw_pointer<ret_val>());
  function("embind_test_return_raw_polymorphic_multiply_derived_ptr_as_base", embind_test_return_raw_polymorphic_multiply_derived_ptr_as_base, allow_raw_pointer<ret_val>());
  function("embind_test_return_raw_polymorphic_multiply_derived_ptr_as_second_base", embind_test_return_raw_polymorphic_multiply_derived_ptr_as_second_base, allow_raw_pointer<ret_val>());
  function("embind_test_return_raw_polymorphic_derived_four_times_not_bound_as_base", embind_test_return_raw_polymorphic_derived_four_times_not_bound_as_base, allow_raw_pointer<ret_val>());
  function("embind_test_return_smart_derived_ptr", embind_test_return_smart_derived_ptr);
  function("embind_test_return_smart_sibling_derived_ptr", embind_test_return_smart_sibling_derived_ptr);
  function("embind_test_return_smart_multiply_derived_ptr", embind_test_return_smart_multiply_derived_ptr);
  function("embind_test_return_smart_derived_thrice_ptr", embind_test_return_smart_derived_thrice_ptr);
  function("embind_test_return_smart_base_ptr", embind_test_return_smart_base_ptr);
  function("embind_test_return_smart_polymorphic_base_ptr", embind_test_return_smart_polymorphic_base_ptr);
  function("embind_test_return_smart_polymorphic_derived_ptr", embind_test_return_smart_polymorphic_derived_ptr);
  function("embind_test_return_smart_polymorphic_sibling_derived_ptr", embind_test_return_smart_polymorphic_sibling_derived_ptr);
  function("embind_test_return_smart_polymorphic_multiply_derived_ptr", embind_test_return_smart_polymorphic_multiply_derived_ptr);
  function("embind_test_return_poly_derived_twice_without_smart_pointer_as_poly_base", embind_test_return_poly_derived_twice_without_smart_pointer_as_poly_base);
  function("embind_test_return_smart_poly_derived_thrice_ptr", embind_test_return_smart_poly_derived_thrice_ptr);
  function("embind_test_return_smart_derived_ptr_as_base", embind_test_return_smart_derived_ptr_as_base);
  function("embind_test_return_smart_derived_ptr_as_val", embind_test_return_smart_derived_ptr_as_val);
  function("embind_test_return_smart_polymorphic_derived_ptr_as_base", embind_test_return_smart_polymorphic_derived_ptr_as_base);
  function("embind_test_return_smart_polymorphic_sibling_derived_ptr_as_base", embind_test_return_smart_polymorphic_sibling_derived_ptr_as_base);
  function("embind_test_get_class_name_via_base_ptr", embind_test_get_class_name_via_base_ptr, allow_raw_pointer<arg<0>>());
  function("embind_test_get_class_name_via_second_base_ptr", embind_test_get_class_name_via_second_base_ptr, allow_raw_pointer<arg<0>>());
  function("embind_test_get_class_name_via_polymorphic_base_ptr", embind_test_get_class_name_via_polymorphic_base_ptr, allow_raw_pointer<arg<0>>());
  function("embind_test_get_class_name_via_polymorphic_second_base_ptr", embind_test_get_class_name_via_polymorphic_second_base_ptr, allow_raw_pointer<arg<0>>());
  // todo: allow_raw_pointer should fail earlier if argument is not a pointer
  function("embind_test_get_class_name_via_smart_base_ptr", embind_test_get_class_name_via_smart_base_ptr);
  function("embind_test_get_class_name_via_reference_to_smart_base_ptr", embind_test_get_class_name_via_reference_to_smart_base_ptr);
  function("embind_test_get_class_name_via_smart_second_base_ptr", embind_test_get_class_name_via_smart_second_base_ptr);
  function("embind_test_get_class_name_via_smart_polymorphic_base_ptr", embind_test_get_class_name_via_smart_polymorphic_base_ptr);
  function("embind_test_get_virtual_class_name_via_smart_polymorphic_base_ptr", embind_test_get_virtual_class_name_via_smart_polymorphic_base_ptr);
  function("embind_test_get_class_name_via_smart_polymorphic_second_base_ptr", embind_test_get_class_name_via_smart_polymorphic_second_base_ptr);
  function("embind_modify_smart_pointer_passed_by_reference", embind_modify_smart_pointer_passed_by_reference);
  function("embind_attempt_to_modify_smart_pointer_when_passed_by_value", embind_attempt_to_modify_smart_pointer_when_passed_by_value);
  function("embind_save_smart_base_pointer", embind_save_smart_base_pointer);

  class_<Base1>("Base1")
    .constructor()
    .function("getField", &Base1::getField)
    ;

  class_<Base2>("Base2")
    .function("getField", &Base2::getField)
    .property("field", &Base2::field2)
    ;

  class_<HasTwoBases, base<Base2>>("HasTwoBases")
    .constructor()
    ;

  class_<CustomStruct>("CustomStruct")
    .constructor<>()
    .property("field", &CustomStruct::field)
    .function("getField", &CustomStruct::getField)
    ;

  enum_<Enum>("Enum")
    .value("ONE", ONE)
    .value("TWO", TWO)
    ;
  function("emval_test_take_and_return_Enum", &emval_test_take_and_return_Enum);

  enum_<EnumClass>("EnumClass")
    .value("ONE", EnumClass::ONE)
    .value("TWO", EnumClass::TWO)
    ;
  function("emval_test_take_and_return_EnumClass", &emval_test_take_and_return_EnumClass);

  function("emval_test_call_function", &emval_test_call_function);

  function("emval_test_return_unique_ptr", &emval_test_return_unique_ptr);

  class_<UniquePtrLifetimeMock>("UniquePtrLifetimeMock");
  function("emval_test_return_unique_ptr_lifetime", &emval_test_return_unique_ptr_lifetime);

  function("emval_test_return_shared_ptr", &emval_test_return_shared_ptr);
  function("emval_test_return_empty_shared_ptr", &emval_test_return_empty_shared_ptr);
  function("emval_test_is_shared_ptr_null", &emval_test_is_shared_ptr_null);

  function("emval_test_return_vector", &emval_test_return_vector);
  function("emval_test_return_vector_of_vectors", &emval_test_return_vector_of_vectors);
  function("emval_test_return_vector_pointers", &emval_test_return_vector_pointers);

  register_vector<std::shared_ptr<StringHolder>>("SharedPtrVector");
  function("emval_test_return_shared_ptr_vector", &emval_test_return_shared_ptr_vector);

  function("get_module_property", &get_module_property);

  register_vector<StringHolder>("StringHolderVector");
  class_<VectorHolder>("VectorHolder")
    .constructor<>()
    .function("get", &VectorHolder::get)
    .function("set", &VectorHolder::set)
    ;

  function("test_string_with_vec", &test_string_with_vec);

#if __cplusplus >= 201703L
  register_optional<int>();
  register_optional<float>();
  register_optional<SmallClass>();
  register_optional<SmallClass*>();
  register_optional<std::string>();
  function("embind_test_return_optional_int", &embind_test_return_optional_int);
  function("embind_test_return_optional_float", &embind_test_return_optional_float);
  function("embind_test_return_optional_small_class", &embind_test_return_optional_small_class);
  function("embind_test_return_optional_small_class_pointer", &embind_test_return_optional_small_class_pointer);
  function("embind_test_return_optional_string", &embind_test_return_optional_string);
  function("embind_test_optional_int_arg", &embind_test_optional_int_arg);
  function("embind_test_optional_float_arg", &embind_test_optional_float_arg);
  function("embind_test_optional_string_arg", &embind_test_optional_string_arg);
  function("embind_test_optional_small_class_arg", &embind_test_optional_small_class_arg);
  function("embind_test_optional_multiple_arg", &embind_test_optional_multiple_arg);
#endif

  register_map<std::string, int>("StringIntMap");
  function("embind_test_get_string_int_map", embind_test_get_string_int_map);

  function("embind_test_getglobal", &embind_test_getglobal);

  function("embind_test_new_Object", &embind_test_new_Object);
  function("embind_test_new_factory", &embind_test_new_factory);

  class_<HasExternalConstructor>("HasExternalConstructor")
    .constructor(&createHasExternalConstructor)
    .function("getString", &HasExternalConstructor::getString)
    ;

  class_<HasExternalConstructorNoCopy>("HasExternalConstructorNoCopy")
    .constructor(&HasExternalConstructorNoCopy::create)
    .function("getInt", &HasExternalConstructorNoCopy::getInt)
    ;

  auto HeldBySmartPtr_class = class_<HeldBySmartPtr>("HeldBySmartPtr");
  HeldBySmartPtr_class
    .smart_ptr<CustomSmartPtr<HeldBySmartPtr>>("CustomSmartPtr<HeldBySmartPtr>")
    .smart_ptr_constructor("shared_ptr<HeldbySmartPtr>", &std::make_shared<HeldBySmartPtr, int, std::string>)
    .class_function("newCustomPtr", HeldBySmartPtr::newCustomPtr)
    .function("returnThis", &takesHeldBySmartPtrSharedPtr)
    .property("i", &HeldBySmartPtr::i)
    .property("s", &HeldBySmartPtr::s)
    ;
  function("takesHeldBySmartPtr", &takesHeldBySmartPtr);
  function("takesHeldBySmartPtrSharedPtr", &takesHeldBySmartPtrSharedPtr);

  class_<HeldByCustomSmartPtr>("HeldByCustomSmartPtr")
    .smart_ptr<std::shared_ptr<HeldByCustomSmartPtr>>("shared_ptr<HeldByCustomSmartPtr>")
    .smart_ptr_constructor("CustomSmartPtr<HeldByCustomSmartPtr>", &HeldByCustomSmartPtr::create)
    .class_function("createSharedPtr", &HeldByCustomSmartPtr::createSharedPtr)
    .property("i", &HeldByCustomSmartPtr::i)
    .property("s", &HeldByCustomSmartPtr::s)
    ;

  function("passThroughRawPtr", &passThroughRawPtr, allow_raw_pointers());
  function("passThroughCustomSmartPtr", &passThroughCustomSmartPtr);

  function("char_to_string", &char_to_string);
  function("signed_char_to_string", &signed_char_to_string);
  function("unsigned_char_to_string", &unsigned_char_to_string);
  function("short_to_string", &short_to_string);
  function("unsigned_short_to_string", &unsigned_short_to_string);
  function("int_to_string", &int_to_string);
  function("unsigned_int_to_string", &unsigned_int_to_string);
  function("long_to_string", &long_to_string);
  function("unsigned_long_to_string", &unsigned_long_to_string);

  function("store_unsigned_char", &store_unsigned_char);
  function("load_unsigned_char", &load_unsigned_char);
  function("store_unsigned_short", &store_unsigned_short);
  function("load_unsigned_short", &load_unsigned_short);
  function("store_unsigned_int", &store_unsigned_int);
  function("load_unsigned_int", &load_unsigned_int);
  function("store_unsigned_long", &store_unsigned_long);
  function("load_unsigned_long", &load_unsigned_long);
}

int overloaded_function(int i) {
  assert(i == 10);
  return 1;
}

int overloaded_function(int i, int j) {
  assert(i == 20);
  assert(j == 20);
  return 2;
}

class MultipleCtors {
public:
  int value = 0;

  MultipleCtors(int i) {
    value = 1;
    assert(i == 10);
  }
  MultipleCtors(int i, int j) {
    value = 2;
    assert(i == 20);
    assert(j == 20);
  }
  MultipleCtors(int i, int j, int k) {
    value = 3;
    assert(i == 30);
    assert(j == 30);
    assert(k == 30);
  }

  int WhichCtorCalled() const {
    return value;
  }
};

class MultipleSmartCtors {
public:
  int value = 0;

  MultipleSmartCtors(int i) {
    value = 1;
    assert(i == 10);
  }
  MultipleSmartCtors(int i, int j) {
    value = 2;
    assert(i == 20);
    assert(j == 20);
  }

  int WhichCtorCalled() const {
    return value;
  }
};

class MultipleOverloads {
public:
  MultipleOverloads() {}

  int value;
  static int staticValue;

  int Func(int i) {
    assert(i == 10);
    value = 1;
    return 1;
  }
  int Func(int i, int j) {
    assert(i == 20);
    assert(j == 20);
    value = 2;
    return 2;
  }

  int WhichFuncCalled() const {
    return value;
  }

  static int StaticFunc(int i) {
    assert(i == 10);
    staticValue = 1;
    return 1;
  }
  static int StaticFunc(int i, int j) {
    assert(i == 20);
    assert(j == 20);
    staticValue = 2;
    return 2;
  }

  static int WhichStaticFuncCalled() {
    return staticValue;
  }
};

int MultipleOverloads::staticValue = 0;

class MultipleOverloadsDerived : public MultipleOverloads {
public:
  MultipleOverloadsDerived() {
  }

  int Func(int i, int j, int k) {
    assert(i == 30);
    assert(j == 30);
    assert(k == 30);
    value = 3;
    return 3;
  }
  int Func(int i, int j, int k, int l) {
    assert(i == 40);
    assert(j == 40);
    assert(k == 40);
    assert(l == 40);
    value = 4;
    return 4;
  }

  static int StaticFunc(int i, int j, int k) {
    assert(i == 30);
    assert(j == 30);
    assert(k == 30);
    staticValue = 3;
    return 3;
  }
  static int StaticFunc(int i, int j, int k, int l) {
    assert(i == 40);
    assert(j == 40);
    assert(k == 40);
    assert(l == 40);
    staticValue = 4;
    return 4;
  }
};

struct MultipleAccessors {
  int getConst() {
    return 1;
  }
  int getConst() const {
    return 2;
  }
  int getConst(int i) const {
    return i;
  }
};

struct ConstAndNonConst {
  void method(int) {
  }

  int method() const {
    return 10;
  }
};

class DummyForOverloads {};

class MultipleOverloadsDependingOnDummy {
public:
  DummyForOverloads dummy() {
    return DummyForOverloads();
  }

  DummyForOverloads dummy(DummyForOverloads d) {
    return d;
  }

  static DummyForOverloads staticDummy() {
    return DummyForOverloads();
  }

  static DummyForOverloads staticDummy(DummyForOverloads d) {
    return d;
  }
};

DummyForOverloads getDummy() {
  return DummyForOverloads();
}

DummyForOverloads getDummy(DummyForOverloads d) {
  return d;
}

EMSCRIPTEN_BINDINGS(overloads) {
  function("overloaded_function", select_overload<int(int)>(&overloaded_function));
  function("overloaded_function", select_overload<int(int, int)>(&overloaded_function));

  class_<MultipleCtors>("MultipleCtors")
    .constructor<int>()
    .constructor<int, int>()
    .constructor<int, int, int>()
    .function("WhichCtorCalled", &MultipleCtors::WhichCtorCalled)
    ;

  class_<MultipleSmartCtors>("MultipleSmartCtors")
    .smart_ptr<std::shared_ptr<MultipleSmartCtors>>("shared_ptr<MultipleSmartCtors>")
    .constructor(&std::make_shared<MultipleSmartCtors, int>)
    .constructor(&std::make_shared<MultipleSmartCtors, int, int>)
    .function("WhichCtorCalled", &MultipleSmartCtors::WhichCtorCalled)
    ;

  class_<MultipleOverloads>("MultipleOverloads")
    .constructor<>()
    .function("Func", select_overload<int(int)>(&MultipleOverloads::Func))
    .function("Func", select_overload<int(int, int)>(&MultipleOverloads::Func))
    .function("WhichFuncCalled", &MultipleOverloads::WhichFuncCalled)
    .class_function("StaticFunc", select_overload<int(int)>(&MultipleOverloads::StaticFunc))
    .class_function("StaticFunc", select_overload<int(int,int)>(&MultipleOverloads::StaticFunc))
    .class_function("WhichStaticFuncCalled", &MultipleOverloads::WhichStaticFuncCalled)
    ;

  class_<MultipleOverloadsDerived, base<MultipleOverloads> >("MultipleOverloadsDerived")
    .constructor<>()
    .function("Func", select_overload<int(int,int,int)>(&MultipleOverloadsDerived::Func))
    .function("Func", select_overload<int(int,int,int,int)>(&MultipleOverloadsDerived::Func))
    .class_function("StaticFunc", select_overload<int(int,int,int)>(&MultipleOverloadsDerived::StaticFunc))
    .class_function("StaticFunc", select_overload<int(int,int,int,int)>(&MultipleOverloadsDerived::StaticFunc))
    ;

  class_<MultipleAccessors>("MultipleAccessors")
    .function("getConst", select_overload<int(int)const>(&MultipleAccessors::getConst))
    ;

  class_<ConstAndNonConst>("ConstAndNonConst")
    .function("method", select_const(&ConstAndNonConst::method))
    ;

  class_<DummyForOverloads>("DummyForOverloads").constructor();

  class_<MultipleOverloadsDependingOnDummy>("MultipleOverloadsDependingOnDummy")
    .constructor()
    .function("dummy", select_overload<DummyForOverloads()>(&MultipleOverloadsDependingOnDummy::dummy))
    .function("dummy", select_overload<DummyForOverloads(DummyForOverloads)>(&MultipleOverloadsDependingOnDummy::dummy))
    .class_function("staticDummy", select_overload<DummyForOverloads()>(&MultipleOverloadsDependingOnDummy::staticDummy))
    .class_function("staticDummy", select_overload<DummyForOverloads(DummyForOverloads)>(&MultipleOverloadsDependingOnDummy::staticDummy))
    ;

  function("getDummy", select_overload<DummyForOverloads(void)>(&getDummy));
  function("getDummy", select_overload<DummyForOverloads(DummyForOverloads)>(&getDummy));
}

// tests for out-of-order registration

class SecondElement {};

class FirstElement {};

struct OrderedTuple {
  FirstElement first;
  SecondElement second;
};

struct OrderedStruct {
  FirstElement first;
  SecondElement second;
};

OrderedTuple getOrderedTuple() {
  return OrderedTuple();
}

OrderedStruct getOrderedStruct() {
  return OrderedStruct();
}

EMSCRIPTEN_BINDINGS(order) {
  value_array<OrderedTuple>("OrderedTuple")
    .element(&OrderedTuple::first)
    .element(&OrderedTuple::second)
    ;

  value_object<OrderedStruct>("OrderedStruct")
    .field("first", &OrderedStruct::first)
    .field("second", &OrderedStruct::second)
    ;

  class_<SecondElement>("SecondElement")
    ;

  class_<FirstElement>("FirstElement")
    ;

  function("getOrderedTuple", &getOrderedTuple);
  function("getOrderedStruct", &getOrderedStruct);
}

// tests for unbound types

template<typename T> T passThrough(T t) {
  return t;
}

struct UnboundClass {};

struct HasUnboundBase : public UnboundClass {
  static void noop() {
  }
};

HasUnboundBase getHasUnboundBase(HasUnboundBase f) {
  return f;
}

struct HasConstructorUsingUnboundArgument {
  HasConstructorUsingUnboundArgument(UnboundClass) {
  }
};

struct SecondUnboundClass {};

struct HasConstructorUsingUnboundArgumentAndUnboundBase
  : public SecondUnboundClass {
  HasConstructorUsingUnboundArgumentAndUnboundBase(UnboundClass) {
  }
};

struct BoundClass {
  UnboundClass method(UnboundClass t) {
    return t;
  }

  static UnboundClass classfunction(UnboundClass t) {
    return t;
  }

  UnboundClass property;
};

#ifndef SKIP_UNBOUND_TYPES
EMSCRIPTEN_BINDINGS(incomplete) {
  constant("hasUnboundTypeNames", emscripten::has_unbound_type_names);

  function("getUnboundClass", &passThrough<UnboundClass>);

  class_<HasUnboundBase, base<UnboundClass>>("HasUnboundBase")
    .class_function("noop", &HasUnboundBase::noop)
    ;
  function("getHasUnboundBase", &passThrough<HasUnboundBase>);

  class_<HasConstructorUsingUnboundArgument>("HasConstructorUsingUnboundArgument")
    .constructor<UnboundClass>()
    ;

  class_<HasConstructorUsingUnboundArgumentAndUnboundBase, base<SecondUnboundClass>>("HasConstructorUsingUnboundArgumentAndUnboundBase")
    .constructor<UnboundClass>()
    ;

  class_<BoundClass>("BoundClass")
    .constructor<>()
    .function("method", &BoundClass::method)
    .class_function("classfunction", &BoundClass::classfunction)
    .property("property", &BoundClass::property)
    ;
}
#endif

class Noncopyable {
  Noncopyable(const Noncopyable&) = delete;
  Noncopyable& operator=(const Noncopyable&) = delete;

public:
  Noncopyable() {
  }
  Noncopyable(Noncopyable&& other) {
    other.valid = false;
  }

  std::string method() const {
    return "foo";
  }

  bool valid = true;
};

Noncopyable getNoncopyable() {
  return Noncopyable();
}

EMSCRIPTEN_BINDINGS(noncopyable) {
  class_<Noncopyable>("Noncopyable")
    .constructor<>()
    .function("method", &Noncopyable::method)
    ;

  function("getNoncopyable", &getNoncopyable, return_value_policy::take_ownership());
}

struct HasReadOnlyProperty {
  HasReadOnlyProperty(int i) : i(i) {
  }

  const int i;
};

EMSCRIPTEN_BINDINGS(read_only_properties) {
  class_<HasReadOnlyProperty>("HasReadOnlyProperty")
    .constructor<int>()
    .property("i", &HasReadOnlyProperty::i)
    ;
}

struct StaticConstIntStruct {
  static const int STATIC_CONST_INTEGER_VALUE_1;
  static const int STATIC_CONST_INTEGER_VALUE_1000;
};

const int StaticConstIntStruct::STATIC_CONST_INTEGER_VALUE_1 = 1;
const int StaticConstIntStruct::STATIC_CONST_INTEGER_VALUE_1000 = 1000;

EMSCRIPTEN_BINDINGS(constants) {
  constant("INT_CONSTANT", 10);

  constant("STATIC_CONST_INTEGER_VALUE_1", StaticConstIntStruct::STATIC_CONST_INTEGER_VALUE_1);
  constant("STATIC_CONST_INTEGER_VALUE_1000", StaticConstIntStruct::STATIC_CONST_INTEGER_VALUE_1000);

  constant("STRING_CONSTANT", std::string("some string"));

  TupleVector tv(1, 2, 3, 4);
  constant("VALUE_ARRAY_CONSTANT", tv);

  StructVector sv(1, 2, 3, 4);
  constant("VALUE_OBJECT_CONSTANT", sv);
}

class DerivedWithOffset : public DummyDataToTestPointerAdjustment,
                          public Base {};

std::shared_ptr<Base>
return_Base_from_DerivedWithOffset(std::shared_ptr<DerivedWithOffset> ptr) {
  return ptr;
}

EMSCRIPTEN_BINDINGS(with_adjustment) {
  class_<DerivedWithOffset, base<Base>>("DerivedWithOffset")
    .smart_ptr_constructor("shared_ptr<DerivedWithOffset>", &std::make_shared<DerivedWithOffset>)
    ;

  function("return_Base_from_DerivedWithOffset", &return_Base_from_DerivedWithOffset);
}

void clear_StringHolder(StringHolder& sh) {
  sh.set("");
}

EMSCRIPTEN_BINDINGS(references) {
  function("clear_StringHolder", &clear_StringHolder);
}

StringHolder return_StringHolder_copy(val func) {
  return func.as<StringHolder>();
}

StringHolder call_StringHolder_func(val func) {
  return func().as<StringHolder>();
}

EMSCRIPTEN_BINDINGS(return_values) {
  function("return_StringHolder_copy", &return_StringHolder_copy);
  function("call_StringHolder_func", &call_StringHolder_func);
}

struct Mixin {
  int get10() const {
    return 10;
  }
};

template<typename ClassBinding>
const ClassBinding& registerMixin(const ClassBinding& binding) {
  // need a wrapper for implicit conversion from DerivedWithMixin to Mixin
  struct Local {
    static int get10(const typename ClassBinding::class_type& self) {
      return self.get10();
    }
  };

  return binding
    .function("get10", &Local::get10)
    ;
}

class DerivedWithMixin : public Base, public Mixin {
};

EMSCRIPTEN_BINDINGS(mixins) {
  registerMixin(
    class_<DerivedWithMixin, base<Base>>("DerivedWithMixin")
      .constructor<>()
  );
}

template<typename T> T val_as(const val& v) {
  return v.as<T>();
}

EMSCRIPTEN_BINDINGS(val_as) {
  function("val_as_bool", &val_as<bool>);
  function("val_as_char", &val_as<char>);
  function("val_as_short", &val_as<short>);
  function("val_as_int", &val_as<int>);
  function("val_as_long", &val_as<long>);

  function("val_as_float", &val_as<float>);
  function("val_as_double", &val_as<double>);

  function("val_as_string", &val_as<std::string>);
  function("val_as_wstring", &val_as<std::wstring>);
  function("val_as_val", &val_as<val>);

  function("val_as_value_object", &val_as<StructVector>);
  function("val_as_value_array", &val_as<TupleVector>);

  function("val_as_enum", &val_as<Enum>);

  // memory_view is always JS -> C++
  // function("val_as_memory_view", &val_as<memory_view>);
}

val construct_with_6(val factory) {
  unsigned char a1 = 6;
  double a2 = -12.5;
  std::string a3("a3");
  StructVector a4(1, 2, 3, 4);
  EnumClass a5 = EnumClass::TWO;
  TupleVector a6(-1, -2, -3, -4);
  return factory.new_(a1, a2, a3, a4, a5, a6);
}

val construct_with_memory_view(val factory) {
  static const char data[11] = "0123456789";
  return factory.new_(
    std::string("before"), typed_memory_view(10, data), std::string("after"));
}

val construct_with_ints_and_float(val factory) {
  return factory.new_(65537, 4.0f, 65538);
}

val construct_with_arguments_before_and_after_memory_growth() {
  auto out = val::array();
  out.set(0, val::global("Uint8Array").new_(5));
  force_memory_growth();
  out.set(1, val::global("Uint8Array").new_(5));
  return out;
}

EMSCRIPTEN_BINDINGS(val_new_) {
  function("construct_with_6_arguments", &construct_with_6);
  function("construct_with_memory_view", &construct_with_memory_view);
  function("construct_with_ints_and_float", &construct_with_ints_and_float);
  function("construct_with_arguments_before_and_after_memory_growth",
           &construct_with_arguments_before_and_after_memory_growth);
}

template<typename T> class intrusive_ptr {
public:
  typedef T element_type;

  intrusive_ptr(std::nullptr_t = nullptr) : px(nullptr) {
  }

  template<typename U> explicit intrusive_ptr(U* px) : px(px) {
    addRef(px);
  }

  intrusive_ptr(const intrusive_ptr& that) : px(that.px) {
    addRef(px);
  }

  template<typename U>
  intrusive_ptr(const intrusive_ptr<U>& that) : px(that.get()) {
    addRef(px);
  }

  intrusive_ptr& operator=(const intrusive_ptr& that) {
    reset(that.get());
    return *this;
  }

  intrusive_ptr& operator=(intrusive_ptr&& that) {
    release(px);
    px = that.px;
    that.px = 0;
    return *this;
  }

  template<typename U> intrusive_ptr& operator=(const intrusive_ptr<U>& that) {
    reset(that.get());
    return *this;
  }

  template<typename U> intrusive_ptr& operator=(intrusive_ptr<U>&& that) {
    release(px);
    px = that.px;
    that.px = 0;
    return *this;
  }

  ~intrusive_ptr() {
    release(px);
  }

  void reset(T* nx = nullptr) {
    addRef(nx);
    release(px);
    px = nx;
  }

  T* get() const {
    return px;
  }

  T& operator*() const {
    return *px;
  }

  T* operator->() const {
    return px;
  }

  explicit operator bool() const {
    return px != nullptr;
  }

  void swap(intrusive_ptr& rhs) {
    std::swap(px, rhs.px);
  }

private:
  void addRef(T* px) {
    if (px) {
      ++px->referenceCount;
    }
  }

  void release(T* px) {
    if (px && --px->referenceCount == 0) {
      delete px;
    }
  }

  T* px;

  template<typename U> friend class intrusive_ptr;
};

namespace emscripten {
template<typename T> struct smart_ptr_trait<intrusive_ptr<T>> {
  typedef intrusive_ptr<T> pointer_type;
  typedef T element_type;

  static sharing_policy get_sharing_policy() {
    return sharing_policy::INTRUSIVE;
  }

  static T* get(const intrusive_ptr<T>& p) {
    return p.get();
  }

  static intrusive_ptr<T> share(const intrusive_ptr<T>& r, T* ptr) {
    return intrusive_ptr<T>(ptr);
  }

  static pointer_type* construct_null() {
    return new pointer_type;
  }
};
} // namespace emscripten

template<typename T> intrusive_ptr<T> make_intrusive_ptr() {
  return intrusive_ptr<T>(new T);
}

struct IntrusiveClass {
  virtual ~IntrusiveClass() {}
  long referenceCount = 0;
};

struct IntrusiveClassWrapper : public wrapper<IntrusiveClass> {
  EMSCRIPTEN_WRAPPER(IntrusiveClassWrapper);
};

template<typename T> struct Holder {
  void set(const T& v) {
    value = v;
  }
  const T& get() const {
    return value;
  }
  T value;
};

EMSCRIPTEN_BINDINGS(intrusive_pointers) {
  class_<IntrusiveClass>("IntrusiveClass")
    .smart_ptr_constructor("intrusive_ptr<IntrusiveClass>", &make_intrusive_ptr<IntrusiveClass>)
    .allow_subclass<IntrusiveClassWrapper, intrusive_ptr<IntrusiveClassWrapper>>("IntrusiveClassWrapper", "IntrusiveClassWrapperPtr")
    ;

  typedef Holder<intrusive_ptr<IntrusiveClass>> IntrusiveClassHolder;
  class_<IntrusiveClassHolder>("IntrusiveClassHolder")
    .constructor<>()
    .function("set", &IntrusiveClassHolder::set)
    .function("get", &IntrusiveClassHolder::get)
    ;

  function("passThroughIntrusiveClass", &passThrough<intrusive_ptr<IntrusiveClass>>);
}

std::string getTypeOfVal(const val& v) {
  return v.typeOf().as<std::string>();
}

EMSCRIPTEN_BINDINGS(typeOf) {
  function("getTypeOfVal", &getTypeOfVal);
}

struct HasStaticMember {
  static const int c;
  static int v;
};

const int HasStaticMember::c = 10;
int HasStaticMember::v = 20;

EMSCRIPTEN_BINDINGS(static_member) {
  class_<HasStaticMember>("HasStaticMember")
    .class_property("c", &HasStaticMember::c)
    .class_property("v", &HasStaticMember::v)
    ;
}
